<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/SDWebImage4/">
<meta property="og:title" content="       SDWebImage Source Probe: Operation | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       SDWebImage Source Probe: Operation | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/SDWebImage4/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>SDWebImage Source Probe: Operation</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-06-22"> 2017-06-22
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>很久没有更博客，最近都在忙着毕业设计和一些小项目，所以学习精力有些分散，读源码的时间大幅度缩水。是时候继续更新了。</p>
<p>在解读 Operation 部分的源码之前，需要先了解一下关于 <code>NSURLSession</code> 的一些知识。</p>
<h2 id="nsurlsession-">对于 NSURLSession 的一些知识</h2>
<p><code>NSURLSession</code> 是于 2013 年随着 iOS 7 一同面世的，苹果公司对于其的定位是作为 <code>NSURLConnection</code> 的替代者，然后逐步推广。现在最广泛使用的第三方网络框架 <code>AFNetworking</code> 以及这套博文分析的 <code>SDWebImage</code> 等等都在使用 <code>NSURLSession</code>。</p>
<p>在 OSI 计算机网络体系结构中，自外向里的第三层<em>会话层</em>，我们可以将 NSURLSession 理解为会话层。这一层通常用于管理网络接口的创建、维护、删除等等工作。</p>
<p><code>NSURLSession</code> 和 <code>NSURLConnection</code> 都提供了与各种协议，例如 HTTP 和 HTTPS，进行交互的 API。在官方文档中对其的描述，这是一种高度可配置的 <code>Container</code>，通过其提供的 API 可以进行很细微的管理控制。<code>NSURLSession</code> 提供了 <code>NSURLConnection</code> 中的所有特性，在功能上可以称之为后者的超集。</p>
<p>使用 <code>NSURLSession</code> 最基本单元就是 <code>Task</code>，这个是 <code>NSURLSessionTask</code> 的实例。有三种类型的任务：<code>NSURLSessionDataTask</code>、<code>NSURLSessionDownloadTask</code> 和 <code>NSURLSessionUploadTask</code>。</p>
<p>我们使用 <code>NSURLSessionDownloadTask</code> 来创建一个下载 <code>Task</code>。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">设置配置类型，这里我们使用默认配置类型，改类型下，会将缓存文件存储在磁盘上</span>
<span class="no">NSURLSessionConfiguration</span> <span class="o">*</span><span class="n">sessionConfiguration</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLSessionConfiguration</span> <span class="n">defaultSessionConfiguration</span><span class="o">]</span><span class="p">;</span>  
<span class="sr">//</span> <span class="err">通过</span> <span class="n">sessionConfiguration</span> <span class="err">创建</span> <span class="n">session</span> <span class="err">对象实例，并设置代理对象</span>
<span class="no">NSURLSession</span> <span class="o">*</span><span class="n">session</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLSession</span> <span class="ss">sessionWithConfiguration</span><span class="p">:</span><span class="n">sessionConfiguration</span> <span class="ss">delegate</span><span class="p">:</span><span class="nb">self</span> <span class="ss">delegateQueue</span><span class="p">:</span><span class="kp">nil</span><span class="o">]</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">通过创建的</span> <span class="n">session</span> <span class="err">对象创建下载</span> <span class="n">task</span><span class="err">，传入需要下载的</span> <span class="n">url</span> <span class="err">属性</span>
<span class="no">NSURLSessionDownloadTask</span> <span class="o">*</span><span class="n">downloadTask</span> <span class="o">=</span> <span class="o">[</span><span class="n">session</span> <span class="ss">downloadTaskWithURL</span><span class="p">:</span><span class="o">[</span><span class="no">NSURL</span> <span class="ss">URLWithString</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;http://www.desgard.com/assets/images/logo-new.png&quot;</span><span class="o">]]</span><span class="p">;</span>  
<span class="sr">//</span> <span class="err">执行给定下载</span> <span class="n">task</span>
<span class="o">[</span><span class="n">downloadTask</span> <span class="n">resume</span><span class="o">]</span><span class="p">;</span></code></pre></div>
<p>通过此例我们了解了 NSURLSession 用于网络会话层工作。是网络接口的管理层对象，在网络数据传输中，起到桥梁的作用。</p>
<h2 id="operation--start-">Operation 的 start 开启方法</h2>
<p>在 Downloader 一文中，我们知道 SDWebImage 下载图片是通过构造一个 Operation(NSOperation) 来实现的，并且会追加放入 downloadQueue ( <code>NSOperationQueue</code> )中。所以下载任务用实例化描述，即一个 Operation。</p>
<p>NSOperation 一般是用来操作或者执行一个单一的任务，如果任务不复杂，其实是可以使用 Cocoa 中的 NSOperation 的派生类 <code>NSBlockOperation</code> 和 <code>NSInvocationOperation</code>。当其无法满足需求时，我们可以像 SDWebImage 一样去定制封装 NSOperation 的子类。关于 NSOperation，又可以归为两类：<strong>并发(concurrent)</strong> 和 <strong>非并发(non-concurrent)</strong>，而在 SDWebImage 中可视作并发类型。</p>
<p>来看 SDWebImage 中对于 <code>start</code> 函数的重写：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">重写</span> <span class="no">NSOperation</span> <span class="err">的</span> <span class="n">start</span> <span class="err">方法</span>
<span class="sr">//</span> <span class="err">更加灵活的管理下载状态，创建下载所使用的</span> <span class="no">NSURLSession</span> <span class="err">对象</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">start</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">互斥锁，保证此时没有其他线程对</span> <span class="nb">self</span> <span class="err">对象进行修改</span>
    <span class="sr">//</span> <span class="err">线程保护作用</span>
    <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">管理下载状态</span>
        <span class="sr">//</span> <span class="err">如果取消状态下载，则更改完成状态</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">finished</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
            <span class="o">[</span><span class="nb">self</span> <span class="n">reset</span><span class="o">]</span><span class="p">;</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
<span class="c1">#if TARGET_OS_IPHONE &amp;&amp; __IPHONE_OS_VERSION_MAX_ALLOWED &gt;= __IPHONE_4_0</span>
        <span class="no">Class</span> <span class="no">UIApplicationClass</span> <span class="o">=</span> <span class="no">NSClassFromString</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;UIApplication&quot;</span><span class="p">);</span>
        <span class="no">BOOL</span> <span class="n">hasApplication</span> <span class="o">=</span> <span class="no">UIApplicationClass</span> <span class="o">&amp;&amp;</span> <span class="o">[</span><span class="no">UIApplicationClass</span> <span class="ss">respondsToSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">sharedApplication</span><span class="p">)</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">后台运行的权限</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">hasApplication</span> <span class="o">&amp;&amp;</span> <span class="o">[</span><span class="nb">self</span> <span class="n">shouldContinueWhenAppEntersBackground</span><span class="o">]</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">__weak</span> <span class="n">__typeof__</span> <span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="n">wself</span> <span class="o">=</span> <span class="nb">self</span><span class="p">;</span>
            <span class="no">UIApplication</span> <span class="o">*</span> <span class="n">app</span> <span class="o">=</span> <span class="o">[</span><span class="no">UIApplicationClass</span> <span class="ss">performSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">sharedApplication</span><span class="p">)</span><span class="o">]</span><span class="p">;</span>
            <span class="sr">//</span> <span class="err">标记一个可以在后台长时间运行的后台任务</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">backgroundTaskId</span> <span class="o">=</span> <span class="o">[</span><span class="n">app</span> <span class="ss">beginBackgroundTaskWithExpirationHandler</span><span class="p">:</span><span class="o">^</span><span class="p">{</span>
                <span class="n">__strong</span> <span class="n">__typeof</span> <span class="p">(</span><span class="n">wself</span><span class="p">)</span> <span class="n">sself</span> <span class="o">=</span> <span class="n">wself</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">当应用程序留给后台的时间快要结束时</span>
                <span class="sr">//</span> <span class="err">执行当前回调</span>
                <span class="sr">//</span> <span class="err">进行清理工作（主线程），如果失败则抛出</span> <span class="n">crash</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">sself</span><span class="p">)</span> <span class="p">{</span>
                    <span class="o">[</span><span class="n">sself</span> <span class="n">cancel</span><span class="o">]</span><span class="p">;</span>
                    <span class="sr">//</span> <span class="err">标记指定的后台任务完成</span>
                    <span class="o">[</span><span class="n">app</span> <span class="ss">endBackgroundTask</span><span class="p">:</span><span class="n">sself</span><span class="o">.</span><span class="n">backgroundTaskId</span><span class="o">]</span><span class="p">;</span>
                    <span class="sr">//</span> <span class="err">销毁后台任务标识符</span>
                    <span class="n">sself</span><span class="o">.</span><span class="n">backgroundTaskId</span> <span class="o">=</span> <span class="no">UIBackgroundTaskInvalid</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
<span class="c1">#endif</span>
        <span class="no">NSURLSession</span> <span class="o">*</span><span class="n">session</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">unownedSession</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">self</span><span class="o">.</span><span class="n">unownedSession</span><span class="p">)</span> <span class="p">{</span>
            <span class="no">NSURLSessionConfiguration</span> <span class="o">*</span><span class="n">sessionConfig</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLSessionConfiguration</span> <span class="n">defaultSessionConfiguration</span><span class="o">]</span><span class="p">;</span>
            <span class="n">sessionConfig</span><span class="o">.</span><span class="n">timeoutIntervalForRequest</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
            <span class="sr">/**</span>
<span class="sr">             *  为这个 task 创建 session</span>
<span class="sr">             *  将 nil 作为 delegate 队列进行传递，以便创建一个 session 对象用于执行 delegate 的串行操作队列</span>
<span class="sr">             *  以完成方法以及 handler 回调方法的调用</span>
<span class="sr">             */</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">ownedSession</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLSession</span> <span class="ss">sessionWithConfiguration</span><span class="p">:</span><span class="n">sessionConfig</span>
                                                              <span class="ss">delegate</span><span class="p">:</span><span class="nb">self</span>
                                                         <span class="ss">delegateQueue</span><span class="p">:</span><span class="kp">nil</span><span class="o">]</span><span class="p">;</span>
            <span class="n">session</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">ownedSession</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">创建数据任务</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">dataTask</span> <span class="o">=</span> <span class="o">[</span><span class="n">session</span> <span class="ss">dataTaskWithRequest</span><span class="p">:</span><span class="nb">self</span><span class="o">.</span><span class="n">request</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">正在执行属性标记</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">executing</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">thread</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSThread</span> <span class="n">currentThread</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">启动任务</span>
    <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">dataTask</span> <span class="n">resume</span><span class="o">]</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">dataTask</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">progressBlock</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">设置默认图片的大小，用未知枚举类型标记</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">progressBlock</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="no">NSURLResponseUnknownLength</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">在主线程中发送下载开始通知</span>
        <span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
            <span class="o">[[</span><span class="no">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="o">]</span> <span class="ss">postNotificationName</span><span class="p">:</span><span class="no">SDWebImageDownloadStartNotification</span> <span class="ss">object</span><span class="p">:</span><span class="nb">self</span><span class="o">]</span><span class="p">;</span>
        <span class="p">});</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">创建失败，直接执行回调部分</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">completedBlock</span><span class="p">)</span> <span class="p">{</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">completedBlock</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">[</span><span class="no">NSError</span> <span class="ss">errorWithDomain</span><span class="p">:</span><span class="no">NSURLErrorDomain</span> <span class="ss">code</span><span class="p">:</span><span class="mi">0</span> <span class="ss">userInfo</span><span class="p">:</span><span class="err">@</span><span class="p">{</span><span class="no">NSLocalizedDescriptionKey</span> <span class="p">:</span> <span class="err">@</span><span class="s2">&quot;Connection can&#39;t be initialized&quot;</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="no">YES</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="c1">#if TARGET_OS_IPHONE &amp;&amp; __IPHONE_OS_VERSION_MAX_ALLOWED &gt;= __IPHONE_4_0</span>
    <span class="sr">//</span> <span class="err">停止在后台的执行操作</span>
    <span class="no">Class</span> <span class="no">UIApplicationClass</span> <span class="o">=</span> <span class="no">NSClassFromString</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;UIApplication&quot;</span><span class="p">);</span>
    <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="no">UIApplicationClass</span> <span class="o">||</span> <span class="o">![</span><span class="no">UIApplicationClass</span> <span class="ss">respondsToSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">sharedApplication</span><span class="p">)</span><span class="o">]</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">backgroundTaskId</span> <span class="o">!=</span> <span class="no">UIBackgroundTaskInvalid</span><span class="p">)</span> <span class="p">{</span>
        <span class="no">UIApplication</span> <span class="o">*</span> <span class="n">app</span> <span class="o">=</span> <span class="o">[</span><span class="no">UIApplication</span> <span class="ss">performSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">sharedApplication</span><span class="p">)</span><span class="o">]</span><span class="p">;</span>
        <span class="o">[</span><span class="n">app</span> <span class="ss">endBackgroundTask</span><span class="p">:</span><span class="nb">self</span><span class="o">.</span><span class="n">backgroundTaskId</span><span class="o">]</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">backgroundTaskId</span> <span class="o">=</span> <span class="no">UIBackgroundTaskInvalid</span><span class="p">;</span>
    <span class="p">}</span>
<span class="c1">#endif</span>
<span class="p">}</span></code></pre></div>
<p>对于一个并发类 NSOperation ，在重写 <code>start</code> 方法时，需要去实现异步(asynchronous) 方式来处理事件。在 SDWebImage 中，可以看到类的成员对象中的 <code>self.thread</code> 来承载 operation 任务的执行动作。并且还处理了后台运行时的状态。</p>
<p>我们来具体剖析一下这段代码：</p>
<p>在 <code>NSOperation</code> 中共有三个状态，这些状态可以及时的判断 <code>SDWebImageDownloaderOperation</code> 是否被取消了。如果取消，则认为该任务已经完成，并且需要及时回收资源，即 <code>reset</code> 方法。使用 <code>NSOperation</code> 需要手动管理以下三个状态：</p>
<ul>
  <li><code>isExecuting</code> - 代表任务正在执行中</li>
  <li><code>isFinished</code> - 代表任务已经完成</li>
  <li><code>isCancelled</code> - 代表任务已经取消执行</li>
</ul>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
	<span class="nb">self</span><span class="o">.</span><span class="n">finished</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
	<span class="o">[</span><span class="nb">self</span> <span class="n">reset</span><span class="o">]</span><span class="p">;</span>
	<span class="k">return</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>接下来一段宏中的代码，以考虑到 App 进入后台中的发生的事。在 SD 中使用了 <code>beginBackgroundTaskWithExpirationHandler:</code> 来申请 App 进入后台后额外的占用时间，所以我们要拿出 <code>UIApplication</code> 这个类，并使用 <code>[UIApplication sharedApplication]</code> 这个单例来调用对应方法。考虑到 iOS 的通用性和版本问题，SD 在调用该单例时进行了<strong>双重检测</strong>：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Class</span> <span class="no">UIApplicationClass</span> <span class="o">=</span> <span class="no">NSClassFromString</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;UIApplication&quot;</span><span class="p">);</span>
<span class="no">BOOL</span> <span class="n">hasApplication</span> <span class="o">=</span> <span class="no">UIApplicationClass</span> <span class="o">&amp;&amp;</span> <span class="o">[</span><span class="no">UIApplicationClass</span> <span class="ss">respondsToSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">sharedApplication</span><span class="p">)</span><span class="o">]</span><span class="p">;</span></code></pre></div>
<p>再之后是系统后台任务的代码，这里来聊一聊 <code>beginBackgroundTaskWithExpirationHandler</code> 这个回调。<code>beginBackgroundTaskWithExpirationHandler</code> 不是意味着立即执行后台任务，它相当于<strong>注册了一个后台任务</strong>，而之后的 <code>handler</code> 表示 <strong>App 在直到后台运行的时机到来后在运行其中的 block 代码逻辑</strong>。所以我们仍旧需要判断下载任务的下载状态，如果下载任务还在进行，就需要取消该任务（<code>cancel</code>方法）。这个方法也是在 <code>SDWebImageDownloaderOperation</code> 中定义的，下午将会介绍。</p>
<p>在做完进入后台情况的处理，也就是图片的“善后处理”之后，进入图片下载的正题部分。下载一个单文件对应的是一次网络请求。所以需要用 <code>NSURLSession</code> 来创建一个 task 处理这个请求。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">NSURLSession</span> <span class="o">*</span><span class="n">session</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">unownedSession</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">self</span><span class="o">.</span><span class="n">unownedSession</span><span class="p">)</span> <span class="p">{</span>
	<span class="no">NSURLSessionConfiguration</span> <span class="o">*</span><span class="n">sessionConfig</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLSessionConfiguration</span> <span class="n">defaultSessionConfiguration</span><span class="o">]</span><span class="p">;</span>
	<span class="n">sessionConfig</span><span class="o">.</span><span class="n">timeoutIntervalForRequest</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
	<span class="sr">/**</span>
<span class="sr">	*  为找个 task 创建 session</span>
<span class="sr">	*  将 nil 作为 delegate 队列进行传递，以便创建一个 session 对象用于执行 delegate 的串行操作队列</span>
<span class="sr">	*  以完成方法以及 handler 回调方法的调用</span>
<span class="sr">	*/</span>
	<span class="nb">self</span><span class="o">.</span><span class="n">ownedSession</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLSession</span> <span class="ss">sessionWithConfiguration</span><span class="p">:</span><span class="n">sessionConfig</span>
                                                    <span class="ss">delegate</span><span class="p">:</span><span class="nb">self</span>
                                               <span class="ss">delegateQueue</span><span class="p">:</span><span class="kp">nil</span><span class="o">]</span><span class="p">;</span>
	<span class="n">session</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">ownedSession</span><span class="p">;</span>
<span class="p">}</span>
<span class="sr">//</span> <span class="err">创建数据任务</span>
<span class="nb">self</span><span class="o">.</span><span class="n">dataTask</span> <span class="o">=</span> <span class="o">[</span><span class="n">session</span> <span class="ss">dataTaskWithRequest</span><span class="p">:</span><span class="nb">self</span><span class="o">.</span><span class="n">request</span><span class="o">]</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">正在执行属性标记</span>
<span class="nb">self</span><span class="o">.</span><span class="n">executing</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
<span class="nb">self</span><span class="o">.</span><span class="n">thread</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSThread</span> <span class="n">currentThread</span><span class="o">]</span><span class="p">;</span></code></pre></div>
<p>首先取出 session 成员，因为我们需要下载多个图片，不需要为每次请求都进行握手操作，所有复用 <code>NSURLSession</code> 对象。如果发现其未初始化，则对其重新配置。在构造方法中，选用 <code>defaultSessionConfiguration</code>，这个是默认的 session 配置，类似于 <code>NSURLConnection</code> 的标准配置，使用硬盘来存储缓存数据。之后创建请求，增加标记，获取当前线程。</p>
<h2 id="cancel-">任务取消 cancel 方法</h2>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">cancel</span> <span class="p">{</span>
    <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">根据线程是否初始化来查看是否有开启下载任务</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">thread</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">在指定线程中调用</span> <span class="n">cancelInternalAndStop</span> <span class="err">方法</span>
            <span class="o">[</span><span class="nb">self</span> <span class="ss">performSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">cancelInternalAndStop</span><span class="p">)</span> <span class="ss">onThread</span><span class="p">:</span><span class="nb">self</span><span class="o">.</span><span class="n">thread</span> <span class="ss">withObject</span><span class="p">:</span><span class="kp">nil</span> <span class="ss">waitUntilDone</span><span class="p">:</span><span class="no">NO</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">else</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">直接调用</span> <span class="n">cancelInternal</span> <span class="err">方法</span>
            <span class="o">[</span><span class="nb">self</span> <span class="n">cancelInternal</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>在 <code>cancel</code> 方法中，会有两种处理手段。如果下载任务处于开启状态，则在该实例的持有进程中调用 <code>cancelInternalAndStop</code> 方法，否则的话则在当前进程调用 <code>cancelInternal</code> 方法。我们来看这两个方法的区别和联系。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">cancelInternalAndStop</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">判断</span> <span class="n">isFinished</span> <span class="err">标识符</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">isFinished</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="o">[</span><span class="nb">self</span> <span class="n">cancelInternal</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">cancelInternal</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">isFinished</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="o">[</span><span class="k">super</span> <span class="n">cancel</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">执行</span> <span class="n">cancel</span> <span class="err">回调</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">cancelBlock</span><span class="p">)</span> <span class="nb">self</span><span class="o">.</span><span class="n">cancelBlock</span><span class="p">();</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">dataTask</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">停止</span> <span class="n">task</span> <span class="err">任务</span>
        <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">dataTask</span> <span class="n">cancel</span><span class="o">]</span><span class="p">;</span>
        <span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
            <span class="sr">//</span> <span class="err">发送通知</span>
            <span class="o">[[</span><span class="no">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="o">]</span> <span class="ss">postNotificationName</span><span class="p">:</span><span class="no">SDWebImageDownloadStopNotification</span> <span class="ss">object</span><span class="p">:</span><span class="nb">self</span><span class="o">]</span><span class="p">;</span>
        <span class="p">});</span>
        <span class="sr">//</span> <span class="err">如果我们启用了</span> <span class="n">cancel</span> <span class="err">方法，则回调方法不会被执行，并且</span> <span class="n">isFinished</span> <span class="err">和</span> <span class="n">isExecuting</span> <span class="err">两个标识属性修改状态</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">isExecuting</span><span class="p">)</span> <span class="nb">self</span><span class="o">.</span><span class="n">executing</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">self</span><span class="o">.</span><span class="n">isFinished</span><span class="p">)</span> <span class="nb">self</span><span class="o">.</span><span class="n">finished</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="no">Operation</span> <span class="err">初始化操作</span>
    <span class="o">[</span><span class="nb">self</span> <span class="n">reset</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>也许你会有疑问，为什么 <code>cancelInternalAndStop</code> 在调用 <code>cancelInternal</code> 之前多此一举的判断了 <code>self.isFinished</code> 标志符的状态？为什么不写成一个方法？其实这是有历史原因的。请看<a href="https://github.com/rs/SDWebImage/commit/5580c78282910716f63210b700c83d3415bdfc08#diff-7519dfc55f22e25bd87d757458d74b82R151">这个链接</a>。其中 L155 我们发现了“惊天的秘密”。这里大概讲述一下这个历史原因：在 <em>SDWebImage</em> 的 <code>v3.7.0</code> 版本及以前，并没有引入 <code>NSURLSession</code> 而是采用的 <code>NSURLConnection</code>。而后者往往是需要与 Runloop 协同使用，因为每个 Connect 会作为一个 Source 添加到当前线程所在的 Runloop 中，并且 Runloop 会对这个 Connect 对象强引用，以保证代理方法可以调用。</p>
<p>在新版本中，由于启用了 <code>NSURLSession</code>，说明 SDWebImage 已经放弃了 iOS 6 及以下的版本。在进行网络请求的处理时更加的安全，这也是历史的必然趋势。当然，笔者也十分开心，因为不用再解读 Runloop 的代码了。😄</p>
<h2 id="sdwebimagemanager--nsurlsessiondatadelegate-">SDWebImageManager 中的 NSURLSessionDataDelegate 代理方法实现</h2>
<p><code>NSURLSessionDataDelegate</code> 代理用于实现数据下载的各种回调。在 SD 中由于要处理图片下载的各种状态，所以需要遵循改代理，并去自行管理代理方法返回结果的不同处理。</p>
<p>在 <code>Response</code> 数据反馈后，都会传给客户端一个 Http 状态码，根据状态码的不同，需要执行不同情况的处理方法。在 <code>NSURLSessionDataDelegate</code> 的代理方法中，即可实现判断状态码的步骤：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">该方法中实现对服务器的响应进行授权</span>
<span class="sr">//</span> <span class="err">实现后执行</span> <span class="n">completionHandler</span> <span class="err">回调</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="ss">URLSession</span><span class="p">:(</span><span class="no">NSURLSession</span> <span class="o">*</span><span class="p">)</span><span class="n">session</span>
          <span class="ss">dataTask</span><span class="p">:(</span><span class="no">NSURLSessionDataTask</span> <span class="o">*</span><span class="p">)</span><span class="n">dataTask</span>
<span class="ss">didReceiveResponse</span><span class="p">:(</span><span class="no">NSURLResponse</span> <span class="o">*</span><span class="p">)</span><span class="n">response</span>
 <span class="ss">completionHandler</span><span class="p">:(</span><span class="n">void</span> <span class="p">(</span><span class="o">^</span><span class="p">)(</span><span class="no">NSURLSessionResponseDisposition</span> <span class="n">disposition</span><span class="p">))</span><span class="n">completionHandler</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">处理返回状态码小于</span><span class="mi">400</span><span class="err">非</span><span class="mi">304</span><span class="err">情况</span>
    <span class="sr">//</span> <span class="mi">304</span> <span class="err">属于未返回结果未修改，也属于正常返回</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">![</span><span class="n">response</span> <span class="ss">respondsToSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">statusCode</span><span class="p">)</span><span class="o">]</span> <span class="o">||</span> <span class="p">(</span><span class="o">[</span><span class="p">((</span><span class="no">NSHTTPURLResponse</span> <span class="o">*</span><span class="p">)</span><span class="n">response</span><span class="p">)</span> <span class="n">statusCode</span><span class="o">]</span> <span class="o">&lt;</span> <span class="mi">400</span> <span class="o">&amp;&amp;</span> <span class="o">[</span><span class="p">((</span><span class="no">NSHTTPURLResponse</span> <span class="o">*</span><span class="p">)</span><span class="n">response</span><span class="p">)</span> <span class="n">statusCode</span><span class="o">]</span> <span class="o">!=</span> <span class="mi">304</span><span class="p">))</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">获取文件长度</span>
        <span class="sr">//</span> <span class="n">expectedContentLength</span> <span class="err">获取的是下载文件长度，而不是整个文件长度</span>
        <span class="no">NSInteger</span> <span class="n">expected</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">expectedContentLength</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">?</span> <span class="p">(</span><span class="no">NSInteger</span><span class="p">)</span><span class="n">response</span><span class="o">.</span><span class="n">expectedContentLength</span> <span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">expectedSize</span> <span class="o">=</span> <span class="n">expected</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">progressBlock</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">执行过程中</span> <span class="n">block</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">progressBlock</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">expected</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">imageData</span> <span class="o">=</span> <span class="o">[[</span><span class="no">NSMutableData</span> <span class="n">alloc</span><span class="o">]</span> <span class="ss">initWithCapacity</span><span class="p">:</span><span class="n">expected</span><span class="o">]</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">response</span> <span class="o">=</span> <span class="n">response</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">在主线程发送通知消息</span>
        <span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
            <span class="o">[[</span><span class="no">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="o">]</span> <span class="ss">postNotificationName</span><span class="p">:</span><span class="no">SDWebImageDownloadReceiveResponseNotification</span> <span class="ss">object</span><span class="p">:</span><span class="nb">self</span><span class="o">]</span><span class="p">;</span>
        <span class="p">});</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">获取状态码</span>
        <span class="no">NSUInteger</span> <span class="n">code</span> <span class="o">=</span> <span class="o">[</span><span class="p">((</span><span class="no">NSHTTPURLResponse</span> <span class="o">*</span><span class="p">)</span><span class="n">response</span><span class="p">)</span> <span class="n">statusCode</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">如果状态吗反馈</span><span class="mi">304</span><span class="err">状态，则代表服务器告知客户端当前接口结果没有发生变化</span>
        <span class="sr">//</span> <span class="err">此时我们</span><span class="n">cancel</span><span class="err">掉当前的</span> <span class="no">Operation</span> <span class="err">然后从缓存中获取图片</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">code</span> <span class="o">==</span> <span class="mi">304</span><span class="p">)</span> <span class="p">{</span>
            <span class="o">[</span><span class="nb">self</span> <span class="n">cancelInternal</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">其他成功状态直接</span> <span class="n">cancel</span> <span class="err">掉</span> <span class="n">task</span> <span class="err">即可</span>
            <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">dataTask</span> <span class="n">cancel</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">在主线程发送通知消息</span>
        <span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
            <span class="o">[[</span><span class="no">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="o">]</span> <span class="ss">postNotificationName</span><span class="p">:</span><span class="no">SDWebImageDownloadStopNotification</span> <span class="ss">object</span><span class="p">:</span><span class="nb">self</span><span class="o">]</span><span class="p">;</span>
        <span class="p">});</span>
        <span class="sr">//</span> <span class="err">调用</span> <span class="n">completedBlock</span><span class="err">，说明任务完成</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">completedBlock</span><span class="p">)</span> <span class="p">{</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">completedBlock</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">[</span><span class="no">NSError</span> <span class="ss">errorWithDomain</span><span class="p">:</span><span class="no">NSURLErrorDomain</span> <span class="ss">code</span><span class="p">:</span><span class="o">[</span><span class="p">((</span><span class="no">NSHTTPURLResponse</span> <span class="o">*</span><span class="p">)</span><span class="n">response</span><span class="p">)</span> <span class="n">statusCode</span><span class="o">]</span> <span class="ss">userInfo</span><span class="p">:</span><span class="kp">nil</span><span class="o">]</span><span class="p">,</span> <span class="no">YES</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">重置</span> <span class="no">Operation</span> <span class="err">示例状态，以便复用</span>
        <span class="o">[</span><span class="nb">self</span> <span class="n">done</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">完成回调</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">completionHandler</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">completionHandler</span><span class="p">(</span><span class="no">NSURLSessionResponseAllow</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>通过 <code>Response</code> 反馈的 Http 状态码，做出了各种操作。当然这里只要判断出状态码，将各个操作对应上即可。而下面的规划进度回调方案中，则是整个回调方法处理图像的核心部分：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">接收到部分数据时候的回调</span>
<span class="sr">//</span> <span class="err">用于规划进度</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="ss">URLSession</span><span class="p">:(</span><span class="no">NSURLSession</span> <span class="o">*</span><span class="p">)</span><span class="n">session</span> <span class="ss">dataTask</span><span class="p">:(</span><span class="no">NSURLSessionDataTask</span> <span class="o">*</span><span class="p">)</span><span class="n">dataTask</span> <span class="ss">didReceiveData</span><span class="p">:(</span><span class="no">NSData</span> <span class="o">*</span><span class="p">)</span><span class="n">data</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">分块写二进制文件</span>
    <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">imageData</span> <span class="ss">appendData</span><span class="p">:</span><span class="n">data</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">进度控制</span>
    <span class="k">if</span> <span class="p">((</span><span class="nb">self</span><span class="o">.</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageDownloaderProgressiveDownload</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">self</span><span class="o">.</span><span class="n">expectedSize</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nb">self</span><span class="o">.</span><span class="n">completedBlock</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">以下代码思路来自于</span> <span class="ss">http</span><span class="p">:</span><span class="sr">//</span><span class="n">www</span><span class="o">.</span><span class="n">cocoaintheshell</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="mi">2011</span><span class="o">/</span><span class="mo">05</span><span class="o">/</span><span class="n">progressive</span><span class="o">-</span><span class="n">images</span><span class="o">-</span><span class="n">download</span><span class="o">-</span><span class="n">imageio</span><span class="o">/</span>
        <span class="sr">//</span> <span class="err">感谢作者</span> <span class="vi">@Nyx0uf</span>
        <span class="sr">//</span> <span class="err">获取图片总大小</span>
        <span class="n">const</span> <span class="no">NSInteger</span> <span class="n">totalSize</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">imageData</span><span class="o">.</span><span class="n">length</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">更新数据源，需要传递所有数据，而不仅是更新的一部分</span>
        <span class="sr">//</span> <span class="no">ImageIO</span> <span class="err">接口之一，通过</span> <span class="no">CGImageSourceCreateWithData</span> <span class="err">对图片二进制文件解码</span>
        <span class="no">CGImageSourceRef</span> <span class="n">imageSource</span> <span class="o">=</span> <span class="no">CGImageSourceCreateWithData</span><span class="p">((</span><span class="n">__bridge</span> <span class="no">CFDataRef</span><span class="p">)</span><span class="nb">self</span><span class="o">.</span><span class="n">imageData</span><span class="p">,</span> <span class="no">NULL</span><span class="p">);</span>
        <span class="sr">//</span> <span class="err">长和宽均为</span><span class="mi">0</span><span class="err">，说明当前下载为第一段数据</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">width</span> <span class="o">+</span> <span class="n">height</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="no">ImageIO</span> <span class="err">接口之一，返回包含尺寸以及其他信息，其他信息例如</span> <span class="no">EXIF</span><span class="err">、</span><span class="no">IPTC</span> <span class="err">等</span>
            <span class="no">CFDictionaryRef</span> <span class="n">properties</span> <span class="o">=</span> <span class="no">CGImageSourceCopyPropertiesAtIndex</span><span class="p">(</span><span class="n">imageSource</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="no">NULL</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">properties</span><span class="p">)</span> <span class="p">{</span>
                <span class="no">NSInteger</span> <span class="n">orientationValue</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">从</span> <span class="n">properties</span> <span class="err">中拿高度</span>
                <span class="no">CFTypeRef</span> <span class="n">val</span> <span class="o">=</span> <span class="no">CFDictionaryGetValue</span><span class="p">(</span><span class="n">properties</span><span class="p">,</span> <span class="n">kCGImagePropertyPixelHeight</span><span class="p">);</span>
                <span class="sr">//</span> <span class="err">获取到后直接记录到</span> <span class="n">height</span> <span class="err">变量中</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="no">CFNumberGetValue</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">kCFNumberLongType</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">height</span><span class="p">);</span>
                <span class="n">val</span> <span class="o">=</span> <span class="no">CFDictionaryGetValue</span><span class="p">(</span><span class="n">properties</span><span class="p">,</span> <span class="n">kCGImagePropertyPixelWidth</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="no">CFNumberGetValue</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">kCFNumberLongType</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">width</span><span class="p">);</span>
                <span class="sr">//</span> <span class="err">从</span> <span class="n">properties</span> <span class="err">中获取图片的其他信息</span>
                <span class="n">val</span> <span class="o">=</span> <span class="no">CFDictionaryGetValue</span><span class="p">(</span><span class="n">properties</span><span class="p">,</span> <span class="n">kCGImagePropertyOrientation</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="no">CFNumberGetValue</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">kCFNumberNSIntegerType</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">orientationValue</span><span class="p">);</span>
                <span class="no">CFRelease</span><span class="p">(</span><span class="n">properties</span><span class="p">);</span>
                <span class="sr">//</span> <span class="err">当我们使用</span> <span class="no">Core</span> <span class="no">Graphics</span> <span class="err">绘制操作时，如果失去了</span> <span class="no">Orientation</span> <span class="no">Information</span>
                <span class="sr">//</span> <span class="err">这说明在</span> <span class="n">initWithCGImage</span> <span class="err">初始化阶段错误（与</span> <span class="n">didCompleteWithError</span> <span class="err">中</span> <span class="n">initWithData</span> <span class="err">不同）</span>
                <span class="sr">//</span> <span class="err">所以需要暂时缓存，延迟传值</span>
                <span class="n">orientation</span> <span class="o">=</span> <span class="o">[[</span><span class="nb">self</span> <span class="n">class</span><span class="o">]</span> <span class="ss">orientationFromPropertyValue</span><span class="p">:(</span><span class="n">orientationValue</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="o">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="n">orientationValue</span><span class="p">)</span><span class="o">]</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">过程中状态</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">width</span> <span class="o">+</span> <span class="n">height</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">totalSize</span> <span class="o">&lt;</span> <span class="nb">self</span><span class="o">.</span><span class="n">expectedSize</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">创建</span> <span class="no">CGImage</span> <span class="err">引用，根据</span> <span class="no">Source</span> <span class="err">状态创建图片</span>
            <span class="no">CGImageRef</span> <span class="n">partialImageRef</span> <span class="o">=</span> <span class="no">CGImageSourceCreateImageAtIndex</span><span class="p">(</span><span class="n">imageSource</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="no">NULL</span><span class="p">);</span>
<span class="sr">//</span> <span class="err">通过宏来判断平台</span>
<span class="c1">#ifdef TARGET_OS_IPHONE</span>
            <span class="sr">//</span> <span class="n">iOS</span> <span class="err">中图像失真的解决方法</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">partialImageRef</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">根据引用来获取图像高度属性</span>
                <span class="n">const</span> <span class="n">size_t</span> <span class="n">partialHeight</span> <span class="o">=</span> <span class="no">CGImageGetHeight</span><span class="p">(</span><span class="n">partialImageRef</span><span class="p">);</span>
                <span class="sr">//</span> <span class="err">创建</span> <span class="no">RGB</span> <span class="err">色彩空间</span>
                <span class="no">CGColorSpaceRef</span> <span class="n">colorSpace</span> <span class="o">=</span> <span class="no">CGColorSpaceCreateDeviceRGB</span><span class="p">();</span>
                <span class="sr">//</span> <span class="err">创建图像空间，【参数定义】见下文分析</span>
                <span class="no">CGContextRef</span> <span class="n">bmContext</span> <span class="o">=</span> <span class="no">CGBitmapContextCreate</span><span class="p">(</span><span class="no">NULL</span><span class="p">,</span> <span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="n">width</span> <span class="o">*</span> <span class="mi">4</span><span class="p">,</span> <span class="n">colorSpace</span><span class="p">,</span> <span class="n">kCGBitmapByteOrderDefault</span> <span class="o">|</span> <span class="n">kCGImageAlphaPremultipliedFirst</span><span class="p">);</span>
                <span class="sr">//</span> <span class="err">释放色彩空间引用</span>
                <span class="no">CGColorSpaceRelease</span><span class="p">(</span><span class="n">colorSpace</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">bmContext</span><span class="p">)</span> <span class="p">{</span>
                    <span class="sr">//</span> <span class="err">图片渲染方法</span>
                    <span class="no">CGContextDrawImage</span><span class="p">(</span><span class="n">bmContext</span><span class="p">,</span> <span class="p">(</span><span class="no">CGRect</span><span class="p">){</span><span class="o">.</span><span class="n">origin</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="o">.</span><span class="mi">0</span><span class="n">f</span><span class="p">,</span> <span class="o">.</span><span class="n">origin</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="o">.</span><span class="mi">0</span><span class="n">f</span><span class="p">,</span> <span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">width</span><span class="p">,</span> <span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">height</span> <span class="o">=</span> <span class="n">partialHeight</span><span class="p">},</span> <span class="n">partialImageRef</span><span class="p">);</span>
                    <span class="no">CGImageRelease</span><span class="p">(</span><span class="n">partialImageRef</span><span class="p">);</span>
                    <span class="sr">//</span> <span class="err">从上下文中创建</span> <span class="no">CGImage</span>
                    <span class="n">partialImageRef</span> <span class="o">=</span> <span class="no">CGBitmapContextCreateImage</span><span class="p">(</span><span class="n">bmContext</span><span class="p">);</span>
                    <span class="no">CGContextRelease</span><span class="p">(</span><span class="n">bmContext</span><span class="p">);</span>
                <span class="p">}</span>
                <span class="k">else</span> <span class="p">{</span>
                    <span class="sr">//</span> <span class="err">色彩空间创建失败，直接释放</span>
                    <span class="no">CGImageRelease</span><span class="p">(</span><span class="n">partialImageRef</span><span class="p">);</span>
                    <span class="n">partialImageRef</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
<span class="c1">#endif</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">partialImageRef</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">通过</span> <span class="no">Core</span> <span class="no">Graphics</span> <span class="err">引用创建图片对象</span>
                <span class="no">UIImage</span> <span class="o">*</span><span class="n">image</span> <span class="o">=</span> <span class="o">[</span><span class="no">UIImage</span> <span class="ss">imageWithCGImage</span><span class="p">:</span><span class="n">partialImageRef</span> <span class="ss">scale</span><span class="p">:</span><span class="mi">1</span> <span class="ss">orientation</span><span class="p">:</span><span class="n">orientation</span><span class="o">]</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">使用图片的</span> <span class="no">URL</span> <span class="err">作为缓存键</span>
                <span class="no">NSString</span> <span class="o">*</span><span class="n">key</span> <span class="o">=</span> <span class="o">[[</span><span class="no">SDWebImageManager</span> <span class="n">sharedManager</span><span class="o">]</span> <span class="ss">cacheKeyForURL</span><span class="p">:</span><span class="nb">self</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">URL</span><span class="o">]</span><span class="p">;</span>
                <span class="sr">//</span> <span class="n">C</span><span class="o">++</span> <span class="err">的</span> <span class="no">SDScaledImageForKey</span> <span class="err">方法入口，用于多倍数缩放图片的处理及缓存</span>
                <span class="no">UIImage</span> <span class="o">*</span><span class="n">scaledImage</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">scaledImageForKey</span><span class="p">:</span><span class="n">key</span> <span class="ss">image</span><span class="p">:</span><span class="n">image</span><span class="o">]</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">解压缩图片</span>
                <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">shouldDecompressImages</span><span class="p">)</span> <span class="p">{</span>
                    <span class="sr">//</span> <span class="err">对图片进行解码</span>
                    <span class="n">image</span> <span class="o">=</span> <span class="o">[</span><span class="no">UIImage</span> <span class="ss">decodedImageWithImage</span><span class="p">:</span><span class="n">scaledImage</span><span class="o">]</span><span class="p">;</span>
                <span class="p">}</span>
                <span class="k">else</span> <span class="p">{</span>
                    <span class="n">image</span> <span class="o">=</span> <span class="n">scaledImage</span><span class="p">;</span>
                <span class="p">}</span>
                <span class="no">CGImageRelease</span><span class="p">(</span><span class="n">partialImageRef</span><span class="p">);</span>
                <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
                    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">completedBlock</span><span class="p">)</span> <span class="p">{</span>
                        <span class="sr">//</span> <span class="err">完成回调，并传出</span> <span class="n">image</span> <span class="err">引用</span>
                        <span class="nb">self</span><span class="o">.</span><span class="n">completedBlock</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="no">NO</span><span class="p">);</span>
                    <span class="p">}</span>
                <span class="p">});</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="no">CFRelease</span><span class="p">(</span><span class="n">imageSource</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">progressBlock</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">过程回调，传出二进制文件已经下载长度和总长度</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">progressBlock</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">imageData</span><span class="o">.</span><span class="n">length</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">expectedSize</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>主要的过程在注释中都有讲述，这里主要说一下注释中标明的一些地方：</p>
<h4 id="section">创建图像空间的函数原型和参数定义</h4>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">CGContextRef</span> <span class="no">CGBitmapContextCreate</span> <span class="p">(</span>
   <span class="n">void</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="sr">//</span> <span class="err">指向要渲染的绘制内存地址，这个内存块的大小至少是（</span><span class="n">bytesPerRow</span><span class="o">*</span><span class="n">height</span><span class="err">）个字节</span>
   <span class="n">size_t</span> <span class="n">width</span><span class="p">,</span> <span class="sr">//</span> <span class="n">bitmap</span> <span class="err">的高度，单位为像素</span>
   <span class="n">size_t</span> <span class="n">height</span><span class="p">,</span> <span class="sr">//</span> <span class="n">bitmap</span> <span class="err">的高度，单位为像素</span>
   <span class="n">size_t</span> <span class="n">bitsPerComponent</span><span class="p">,</span> <span class="sr">//</span> <span class="err">内存中像素的每个组件的位数，例如</span> <span class="mi">32</span> <span class="err">位像素格式和</span> <span class="no">RGB</span> <span class="err">颜色空间这个值设定为</span> <span class="mi">8</span>
   <span class="n">size_t</span> <span class="n">bytesPerRow</span><span class="p">,</span> <span class="sr">//</span> <span class="n">bitmap</span> <span class="err">的每一行在内存所占的比特数</span>
   <span class="no">CGColorSpaceRef</span> <span class="n">colorspace</span><span class="p">,</span> <span class="sr">//</span> <span class="n">bitmap</span><span class="err">上下文使用的颜色空间</span>
   <span class="no">CGBitmapInfo</span> <span class="n">bitmapInfo</span> <span class="sr">//</span> <span class="err">指定</span><span class="n">bitmap</span><span class="err">是否包含</span><span class="n">alpha</span><span class="err">通道，像素中</span><span class="n">alpha</span><span class="err">通道的相对位置，像素组件是整形还是浮点型等信息的字符串。</span>
<span class="p">);</span></code></pre></div>
<p>当调用这个函数的时候，Quartz 创建一个一个位图绘制环境，也就是位图上下文。当你向上下文中绘制信息时，Quartz 把你要绘制的信息作为位图数据绘制到指定的内存块。</p>
<h4 id="imageio-">imageIO 简介</h4>
<p>之前也许你会惊讶于 SD 库对于图片下载进度的处理，其实这些处理都是交给了 Apple 的 Core Graphics 中的 imageIO 部分组件。在处理进度其实是 imageIO 的<strong>渐进加载图片功能</strong>，这里献上<a href="https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/ImageIOGuide/imageio_source/ikpg_source.html#//apple_ref/doc/uid/TP40005462-CH218-SW3">官方文档</a>。渐进加载图片的过程，只需要创建一个 imageSource 引用即可完成。在上面的源码中也是如此实现的。</p>
<blockquote>
  <p>对于渐进加载，现在已经有很多解决方法。例如 <code>YYWebImage</code> 中已经支持了多种渐进式图片加载方案，而不是传统的 <code>baseline</code> 方式，<a href="http://blog.ibireme.com/2015/11/02/ios_image_tips/">文章链接</a>。</p>
</blockquote>
<h2 id="section-1">总结</h2>
<p>SD 前面的所有流程，其实都在围绕着这个 Operation 展开的。在 Operation 中处理了关键的网络请求及下载部分，而且其会话的控制全部由 Operation 进行持有和处理。这里关系到多线程和网络的基础知识，如果想进一步了解其实现原理，可以补充一下基础知识。</p>
<h2 id="section-2">引文</h2>
<p><a href="http://blog.csdn.net/wsxzk123/article/details/44184309">imageIO—完成渐进加载图片</a></p>
<p><a href="http://greenchiu.github.io/blog/2013/08/06/ren-shi-nsoperation/">认识 Operation</a></p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《SDWebImage Source Probe: Operation》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/SDWebImage4/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/SDWebImage4/&amp;title=《SDWebImage Source Probe: Operation》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/SDWebImage4/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/SDWebImage4/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>